package com.kkkk.rest.common.interceptor;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;


/**
 * LoggerInterceptor
 * 用于在每个Restful API调用前和调用后，输出一条日志
 */
@Component
@ConfigurationProperties("app.http.logger")
public class LoggerInterceptor implements HandlerInterceptor {

    /**
     * 在HttpServletRequest中记录开始时间的属性名。这里随便取一个字符串的名字是为了不和正常的属性有冲突
     */
    private final static String START_TIME_ATTRIBUTE_NAME = "Sa87zY";
    /**
     * 默认filter数，等于1时不进行过滤
     */
    private final int defaultLogFilterCount = 1;
    /**
     * 计数器，每一次符合答应条件的请求就自增，等于logCount的值便清零，分别统计start和end
     */
    private ThreadLocal<ThreadCount> preLogCount;
    private ThreadLocal<ThreadCount> afterLogCount;

    /**
     * 一个请求花费的时间大于此值时才会输出after日志
     */
    private int afterUsed = 100;

    /**
     * 是否开启pre日志记录
     */
    private boolean preRequestLog = false;
    /**
     * 是否开启end 那条日志记录
     */
    private boolean afterResponseLog = false;
    /**
     * pre多少条日志打印一次
     */
    private int preLogFilterCount = defaultLogFilterCount;
    /**
     * after多少条日志打印一次
     */
    private int afterLogFilterCount = defaultLogFilterCount;


    private Logger logger = LoggerFactory.getLogger(LoggerInterceptor.class);

    @PostConstruct
    public void init() {
        preLogCount = ThreadLocal.withInitial(ThreadCount::new);
        afterLogCount = ThreadLocal.withInitial(ThreadCount::new);
    }

    @PreDestroy
    public void destroy() {
        preLogCount.remove();
        afterLogCount.remove();
    }


    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        String url = httpServletRequest.getRequestURI();
        String host = httpServletRequest.getLocalAddr();
        int port = httpServletRequest.getLocalPort();
        String httpMethod = httpServletRequest.getMethod();
        String parameter = httpServletRequest.getQueryString();
        if (o instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) o;
            String method = handlerMethod.getMethod().getName();
            Long start = System.currentTimeMillis();
            httpServletRequest.setAttribute(START_TIME_ATTRIBUTE_NAME, start);
            //判断是否开启了打印start日志，日志过滤则进行count计算，默认为1的情况下不进行计数
            if (preRequestLog && beginCount(preLogCount, preLogFilterCount)) {
                if (StringUtils.isEmpty(parameter)) {
                    logger.info("{} {}:{}{} started, method={}", httpMethod, host, port, url, method);
                } else {
                    logger.info("{} {}:{}{}?{} started, method={}", httpMethod, host, port, url, parameter, method);
                }
            }
        }
        return true;
    }

    private boolean beginCount(ThreadLocal<ThreadCount> threadLocalCount, int logCount) {
        if (logCount == defaultLogFilterCount) {
            return true;
        }
        if (logCount < defaultLogFilterCount) {
            return false;
        }
        ThreadCount threadCount = threadLocalCount.get();
        int currentCount = threadCount.increaseAndGet();
        if (currentCount == logCount) {
            threadCount.clear();
            return true;
        }
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        if (afterResponseLog) {
            String url = httpServletRequest.getRequestURI();
            String host = httpServletRequest.getLocalAddr();
            int port = httpServletRequest.getLocalPort();
            String httpMethod = httpServletRequest.getMethod();
            String parameter = httpServletRequest.getQueryString();
            Object attribute = httpServletRequest.getAttribute(START_TIME_ATTRIBUTE_NAME);
            if (attribute != null) {
                long usedTime = System.currentTimeMillis() - (Long) attribute;
                //判断是否需要打印结束日志，日志过滤则进行count计算，默认为1的情况下不进行计数
                if ((usedTime > afterUsed) && beginCount(afterLogCount, afterLogFilterCount)) {
                    if (StringUtils.isEmpty(parameter)) {
                        logger.info("{} {}:{}{} ended, used {} ms, ", httpMethod, host, port, url, usedTime);
                    } else {
                        logger.info("{} {}:{}{}?{} ended, used {} ms, ", httpMethod, host, port, url, parameter, usedTime);
                    }
                }
            }
        }
    }


    public boolean isPreRequestLog() {
        return preRequestLog;
    }

    public void setPreRequestLog(boolean preRequestLog) {
        this.preRequestLog = preRequestLog;
    }

    public boolean isAfterResponseLog() {
        return afterResponseLog;
    }

    public void setAfterResponseLog(boolean afterResponseLog) {
        this.afterResponseLog = afterResponseLog;
    }

    public int getAfterUsed() {
        return afterUsed;
    }

    public void setAfterUsed(int afterUsed) {
        this.afterUsed = afterUsed;
    }

    public int getPreLogFilterCount() {
        return preLogFilterCount;
    }

    public void setPreLogFilterCount(int preLogFilterCount) {
        this.preLogFilterCount = preLogFilterCount;
    }

    public int getAfterLogFilterCount() {
        return afterLogFilterCount;
    }

    public void setAfterLogFilterCount(int afterLogFilterCount) {
        this.afterLogFilterCount = afterLogFilterCount;
    }

    public static class ThreadCount {
        private int count;

        int increaseAndGet() {
            return ++this.count;
        }

        void clear() {
            this.count = 0;
        }
    }
}
